package org.test4j.mock.faking.util;

import org.test4j.mock.MockUp;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.test4j.mock.faking.modifier.FakeTransformer.notMockType;
import static org.test4j.mock.faking.util.AsmConstant.*;

/**
 * java class处理工具类
 *
 * @author darui.wu
 */
public final class TypeUtility {
    public static final Class[] NO_PARAMETERS = new Class[0];

    public static final Map<Class, Class> PRIMITIVE_TO_WRAPPER = new HashMap<>();
    public static final Map<Class, Class> WRAPPER_TO_PRIMITIVE = new HashMap<>();
    public static final Map<String, String> PRIMITIVE_DESC_ABBR = new HashMap<>();
    public static final Map<String, Class> PRIMITIVE_CLASS = new HashMap<>();
    public static final Map<String, String> ABBR_TYPE_DESC = new HashMap<>();

    static {
        WRAPPER_TO_PRIMITIVE.put(Boolean.class, boolean.class);
        WRAPPER_TO_PRIMITIVE.put(Character.class, char.class);
        WRAPPER_TO_PRIMITIVE.put(Byte.class, byte.class);
        WRAPPER_TO_PRIMITIVE.put(Short.class, short.class);
        WRAPPER_TO_PRIMITIVE.put(Integer.class, int.class);
        WRAPPER_TO_PRIMITIVE.put(Float.class, float.class);
        WRAPPER_TO_PRIMITIVE.put(Long.class, long.class);
        WRAPPER_TO_PRIMITIVE.put(Double.class, double.class);

        PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class);
        PRIMITIVE_TO_WRAPPER.put(char.class, Character.class);
        PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class);
        PRIMITIVE_TO_WRAPPER.put(short.class, Short.class);
        PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class);
        PRIMITIVE_TO_WRAPPER.put(float.class, Float.class);
        PRIMITIVE_TO_WRAPPER.put(long.class, Long.class);
        PRIMITIVE_TO_WRAPPER.put(double.class, Double.class);

        PRIMITIVE_DESC_ABBR.put("void", "V");
        PRIMITIVE_DESC_ABBR.put("boolean", "Z");
        PRIMITIVE_DESC_ABBR.put("char", "C");
        PRIMITIVE_DESC_ABBR.put("byte", "B");
        PRIMITIVE_DESC_ABBR.put("short", "S");
        PRIMITIVE_DESC_ABBR.put("int", "I");
        PRIMITIVE_DESC_ABBR.put("float", "F");
        PRIMITIVE_DESC_ABBR.put("long", "J");
        PRIMITIVE_DESC_ABBR.put("double", "D");
        ABBR_TYPE_DESC.put("V", "void");
        ABBR_TYPE_DESC.put("Z", "boolean");
        ABBR_TYPE_DESC.put("C", "char");
        ABBR_TYPE_DESC.put("B", "byte");
        ABBR_TYPE_DESC.put("S", "short");
        ABBR_TYPE_DESC.put("I", "int");
        ABBR_TYPE_DESC.put("F", "float");
        ABBR_TYPE_DESC.put("J", "long");
        ABBR_TYPE_DESC.put("D", "double");

        PRIMITIVE_CLASS.put("void", void.class);
        PRIMITIVE_CLASS.put("boolean", boolean.class);
        PRIMITIVE_CLASS.put("char", char.class);
        PRIMITIVE_CLASS.put("byte", boolean.class);
        PRIMITIVE_CLASS.put("short", short.class);
        PRIMITIVE_CLASS.put("int", int.class);
        PRIMITIVE_CLASS.put("float", float.class);
        PRIMITIVE_CLASS.put("long", long.class);
        PRIMITIVE_CLASS.put("double", double.class);
    }

    private TypeUtility() {
    }

    public static String classPath(Class clazz) {
        if (clazz == null) {
            return null;
        }
        String className = clazz.getName();
        /** Discards an invalid numerical suffix from a synthetic Java 8 class, if detected **/
        int p = className.indexOf('/');
        if (p > 0) {
            className = className.substring(0, p);
        }
        return classPath(className);
    }

    public static String classPath(String clazz) {
        return clazz.replace('.', '/');
    }

    /**
     * 返回raw class
     *
     * @param declaredType
     * @return
     */
    public static Class getClassType(Type declaredType) {
        while (true) {
            Class rawType = getRawType(declaredType);
            if (rawType != null) {
                return rawType;
            } else if (declaredType instanceof GenericArrayType) {
                declaredType = ((GenericArrayType) declaredType).getGenericComponentType();
            } else if (declaredType instanceof TypeVariable) {
                declaredType = ((TypeVariable) declaredType).getBounds()[0];
            } else if (declaredType instanceof WildcardType) {
                declaredType = ((WildcardType) declaredType).getUpperBounds()[0];
            } else {
                throw new IllegalArgumentException("Type of unexpected kind: " + declaredType);
            }
        }
    }

    /**
     * 返回Class 和 ParameterizedType的raw type
     * 否则返回null
     *
     * @param declaredType
     * @return
     */
    public static Class getRawType(Type declaredType) {
        if (declaredType instanceof Class) {
            return (Class) declaredType;
        } else if (declaredType instanceof ParameterizedType) {
            return (Class) ((ParameterizedType) declaredType).getRawType();
        } else {
            return null;
        }
    }

    /**
     * 根据方法描述 "(Ljava/lang/String;)V" 解析出参数类型列表
     *
     * @param methodDesc
     * @return
     */
    public static Class[] getParameterTypes(String methodDesc) {
        g_asm.org.objectweb.asm.Type[] paramTypes = g_asm.org.objectweb.asm.Type.getArgumentTypes(methodDesc);
        if (paramTypes.length == 0) {
            return NO_PARAMETERS;
        }

        Class[] paramClasses = new Class[paramTypes.length];
        for (int i = 0; i < paramTypes.length; i++) {
            paramClasses[i] = AsmType.getClassForType(paramTypes[i]);
        }
        return paramClasses;
    }

    public static Class getPrimitiveType(Class wrapperType) {
        return WRAPPER_TO_PRIMITIVE.get(wrapperType);
    }

    /**
     * <pre>
     * 获得 toMockType类型
     *
     * new MockUp<ToMockType>(){
     * }
     * </pre>
     *
     * @param fakeClass
     * @return
     */
    public static Type getTypeToFake(Class fakeClass) {
        do {
            Type superclass = fakeClass.getGenericSuperclass();
            if (superclass instanceof ParameterizedType) {
                Type[] types = ((ParameterizedType) superclass).getActualTypeArguments();
                return types[0];
            }
            if (superclass == MockUp.class) {
                throw new IllegalArgumentException("No target type");
            }
            fakeClass = (Class) superclass;
        }
        while (true);
    }

    /**
     * 返回type的类型描述, example:
     * <pre>
     * Ljava.lang.Object;
     * Ljava.util.List<Ljava.lang.String;>;
     * </pre>
     *
     * @param type
     * @return
     */
    public static String getTypeSignature(Type type) {
        StringBuilder signature = new StringBuilder(100);
        Class rawClazz = getClassType(type);
        signature.append("L" + classPath(rawClazz));
        if (type instanceof ParameterizedType) {
            Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
            if (typeArguments.length > 0) {
                signature.append('<');
                for (Type typeArg : typeArguments) {
                    if (typeArg instanceof Class) {
                        Class classArg = (Class) typeArg;
                        signature.append('L').append(classPath(classArg)).append(';');
                    } else {
                        signature.append('*');
                    }
                }
                signature.append('>');
            }
        }
        return signature.append(';').toString();
    }

    /**
     * Appends the descriptor of the given class to the given string builder.
     *
     * @param clazzName the class whose descriptor must be computed.
     */
    public static String descriptor(final String clazzName, Map<String, String> varMaps) {
        if (PRIMITIVE_DESC_ABBR.containsKey(clazzName)) {
            return PRIMITIVE_DESC_ABBR.get(clazzName);
        } else if (varMaps != null && varMaps.containsKey(clazzName)) {
            return varMaps.get(clazzName);
        }
        StringBuilder buff = new StringBuilder();
        if (clazzName.endsWith("[]")) {
            buff.append("[");
            buff.append(descriptor(clazzName.substring(0, clazzName.length() - 2), varMaps));
        } else {
            buff.append("L");
            int pos = clazzName.indexOf('<');
            if (pos > 0) {
                buff.append(classPath(clazzName.substring(0, pos)));
            } else {
                buff.append(classPath(clazzName));
            }
            buff.append(";");
        }
        return buff.toString();
    }

    public static boolean isConstructor(String name) {
        return Method_$Init.equals(name) || Method_Init.equals(name);
    }

    private static List<String> CONSTRUCTOR_STATIC_BLOCK = Arrays.asList(
        Method_$Init,
        Method_Init,
        Method_$CL_Init,
        Method_CL_INIT
    );

    public static boolean isSynthetic(int access) {
        return (access & AsmConstant.SYNTHETIC) != 0;
    }

    /**
     * 返回类自身+所有父类+所有接口
     *
     * @param clazz
     * @return
     */
    public static Set<Class> findAllClass(Class clazz) {
        Set<Class> all = new HashSet<>();
        if (clazz == null) {
            return all;
        }
        Queue<Class> queue = new LinkedList<>();
        queue.offer(clazz);
        while (!queue.isEmpty()) {
            Class curr = queue.poll();
            if (curr == null || notMockType(curr.getName())) {
                continue;
            }
            all.add(curr);
            queue.offer(curr.getSuperclass());
            for (Class _interface : curr.getInterfaces()) {
                queue.offer(_interface);
            }
        }
        return all;
    }

    /**
     * 返回封装后的类型(非基本类型)
     *
     * @param type
     * @return
     */
    public static Class getBoxingType(Class type) {
        if (PRIMITIVE_TO_WRAPPER.containsKey(type)) {
            return PRIMITIVE_TO_WRAPPER.get(type);
        } else {
            return type;
        }
    }

    /**
     * 返回未实现的接口方法
     *
     * @param clazz
     * @return
     */
    public static List<Method> findInterfaceMethod(Class clazz) {
        return Stream.of(clazz.getMethods())
            .filter(m -> Modifier.isAbstract(m.getModifiers()))
            .filter(m -> m.getDeclaringClass().isInterface())
            .collect(Collectors.toList());
    }


    public static boolean isAssignable(Class pType, Class cType) {
        Class pClass = TypeUtility.getBoxingType(pType);
        Class cClass = TypeUtility.getBoxingType(cType);
        if (pClass.equals(Object.class) || cClass.equals(Object.class)) {
            return true;
        } else if (Objects.equals(pClass, cClass)) {
            return true;
        } else if (pClass.isAssignableFrom(cType)) {
            return true;
        }
        return false;
    }

    /**
     * <p>
     * Gets the class name minus the package name for an <code>Object</code>.
     * </p>
     *
     * @param object      the class to get the short name for, may be null
     * @param valueIfNull the value to return if null
     * @return the class name of the object without the package name, or the
     * null value
     */
    public static String getShortClassName(Object object, String valueIfNull) {
        if (object == null) {
            return valueIfNull;
        }
        return getShortClassName(object.getClass());
    }

    /**
     * <p>
     * Gets the class name minus the package name from a <code>Class</code>.
     * </p>
     *
     * @param cls the class to get the short name for.
     * @return the class name without the package name or an empty string
     */
    public static String getShortClassName(Class cls) {
        if (cls == null) {
            return EMPTY_STR;
        }
        return getShortClassName(cls.getName());
    }

    /**
     * <p>
     * Gets the class name minus the package name from a String.
     * </p>
     *
     * <p>
     * The string passed in is assumed to be a class name - it is not checked.
     * </p>
     *
     * @param className the className to get the short name for
     * @return the class name of the class without the package name or an empty
     * string
     */
    public static String getShortClassName(String className) {
        if (className == null) {
            return EMPTY_STR;
        }
        if (className.length() == 0) {
            return EMPTY_STR;
        }

        StringBuffer arrayPrefix = new StringBuffer();
        // Handle array encoding
        if (className.startsWith("[")) {
            while (className.charAt(0) == '[') {
                className = className.substring(1);
                arrayPrefix.append("[]");
            }
            // Strip Object type encoding
            if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
                className = className.substring(1, className.length() - 1);
            }
        }
        if (ABBR_TYPE_DESC.containsKey(className)) {
            className = ABBR_TYPE_DESC.get(className);
        }
        int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
        int innerIdx = className.indexOf(INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
        String out = className.substring(lastDotIdx + 1);
        if (innerIdx != -1) {
            out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
        }
        return out + arrayPrefix;
    }
}